package auth

import (
	"errors"
	"fmt"
	"github.com/dgrijalva/jwt-go/v4"
	jwtTime "github.com/dgrijalva/jwt-go/v4"
	"smart-sensor-cloud/pkg/auth/store/redis"
	"time"
)

type (
	JWTGenerator struct {
		SigningKey   []byte
		RedisManager *redis.Manager
		RedisEnabled bool
	}

	UserClaims struct {
		UserID   int    `json:"user_id"`
		RoleID   int    `json:"role_id"`
		Username string `json:"username"`
		jwt.StandardClaims
	}
)

const (
	DefaultJWTIssuer = "LinkThing - Smart Sensor Cloud"
)

var (
	ErrTokenInvalid = errors.New("invalid token")
)

func (j *JWTGenerator) CreateJWT(claims *UserClaims) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, *claims)
	return token.SignedString(j.SigningKey)
}

func (j *JWTGenerator) ParseTokenToClaims(token string) (*UserClaims, error) {
	principal, err := jwt.ParseWithClaims(token, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
		return j.SigningKey, nil
	})

	tokenMalformedErr := new(jwt.MalformedTokenError)
	tokenExpiredErr := new(jwt.TokenExpiredError)
	tokenNotValidYetErr := new(jwt.TokenNotValidYetError)

	if err != nil {
		if errors.As(err, &tokenMalformedErr) {
			return nil, fmt.Errorf("invalid token: %w", err)
		} else if errors.As(err, &tokenExpiredErr) {
			return nil, fmt.Errorf("token has been expired: %w", err)
		} else if errors.As(err, &tokenNotValidYetErr) {
			return nil, fmt.Errorf("token not valid yet: %w", err)
		} else {
			return nil, ErrTokenInvalid
		}
	}

	if claims, ok := principal.Claims.(*UserClaims); ok && principal.Valid {
		return claims, nil
	}
	return nil, ErrTokenInvalid
}

func (j *JWTGenerator) RenewToken(token string) (string, error) {
	jwt.TimeFunc = func() time.Time {
		return time.Unix(0, 0)
	}

	principal, err := jwt.ParseWithClaims(token, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
		return j.SigningKey, nil
	})

	tokenMalformedErr := new(jwt.MalformedTokenError)
	tokenExpiredErr := new(jwt.TokenExpiredError)
	tokenNotValidYetErr := new(jwt.TokenNotValidYetError)

	if err != nil {
		if errors.As(err, &tokenMalformedErr) {
			return "", fmt.Errorf("invalid token: %w", err)
		} else if errors.As(err, &tokenExpiredErr) {
			return "", fmt.Errorf("token has been expired: %w", err)
		} else if errors.As(err, &tokenNotValidYetErr) {
			return "", fmt.Errorf("token not valid yet: %w", err)
		} else {
			return "", ErrTokenInvalid
		}
	}

	if claims, ok := principal.Claims.(*UserClaims); ok && principal.Valid {
		jwt.TimeFunc = time.Now
		claims.StandardClaims.ExpiresAt = jwtTime.At(time.Now().Add(60 * time.Minute))
		return j.CreateJWT(claims)
	}
	return "", ErrTokenInvalid
}

func (j *JWTGenerator) DestroyToken(token string) error {
	claims, err := j.ParseTokenToClaims(token)
	if err != nil {
		return err
	}

	if j.RedisEnabled {
		expired := time.Unix(claims.ExpiresAt.Unix(), 0).Sub(time.Now())
		return j.RedisManager.Set(token, token, expired)
	}
	return nil
}
